{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python 3.5.5 :: Anaconda custom (64-bit)\n"
     ]
    }
   ],
   "source": [
    "!python -V"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 驴啃计算器\n",
    "\n",
    "首先让我们明确这道题的限制和目标，题目中给出了 20 个计算器上常见的一元函数，你需要通过这些一元函数实现逼近任意给定的浮点数。\n",
    "\n",
    "一元函数有：sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, exp, log, x^2, sqrt, -x, 1/x, R2D, D2R，我们把这个集合记为 $F$\n",
    "\n",
    "形式化来说，对于任意 $y_0 \\in \\mathbb{R}$，构造 $\\{f_k\\}$ $(f_k \\in F, k=1,...,n)$ 序列，记 $y = (f_n \\circ f_{n-1} \\circ ... \\circ f_1)(0) = f_n(f_{n-1}(...f_1(0)))$，满足 $|y-y_0| < \\epsilon$\n",
    "\n",
    "本题中为了降低难度，取 $\\epsilon = 10^{-5}$ 且 $y_0 \\in (0, 100)$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 解法一：搜索\n",
    "\n",
    "一开始没有想到搜索能完成这道题目，但是在进行流量分析是发现有的 payload 明显没有模式，要么解题者是自己在草稿纸上算出来的，要么是搜索的结果，搜索算法很简单，就是枚举 $f$ 序列即可，但可能要进行适量的剪枝或者固定某些步骤的优化，在本地判断后没有问题，就可以提交了。注意：如果遇到搜不出来的情况，可以提交一个错误答案后尝试新的。\n",
    "\n",
    "这个解法适用的情况很窄，如果取 $\\epsilon = 10^{-9}$ 甚至我们只要求 $\\epsilon > 0$，暴力搜索就不太可能做到。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 解法二：二进制逼近\n",
    "\n",
    "观察题目中的这些函数，我们能很快构造出让屏幕上数字乘 2，除以 2 的序列（这里函数顺序为按键顺序）：\n",
    "\n",
    "乘 2：$x * 2 = exp, x^2, log$\n",
    "\n",
    "除以 2：$x / 2 = exp, sqrt, log$\n",
    "\n",
    "有了这两个新的「一元函数」，我们很容易联想到任何数字都有二进制表示，只要我们能加 1，就能通过一个数的二进制表示来构造任何数字，那么问题是怎么加 1 呢？\n",
    "\n",
    "我们注意到有双曲函数恒等式：\n",
    "\n",
    "$\\cosh 2x\\ = \\cosh^2 x + \\sinh^2 x = 2\\cosh^2 x - 1 = 2\\sinh^2 x + 1$\n",
    "\n",
    "俗话说：一寸长，一寸强，这么长的公式肯定强。 \n",
    "\n",
    "我们将想要的目标形式 $x + 1$ 和这个公式放在一起观察。不难发现：\n",
    "\n",
    "令 $t = 2\\sinh^2 x$，即 $x = \\rm{asinh}(\\rm{sqrt}(t/2))$，那么：\n",
    "\n",
    "$t + 1 = \\cosh 2x = \\cosh(2(\\rm{asinh}(\\rm{sqrt}(t/2))))$，换回去：\n",
    "\n",
    "$x + 1 = \\cosh(2(\\rm{asinh}(\\rm{sqrt}(x/2))))$\n",
    "\n",
    "注意到右边用到了我们发现的两个新一元函数乘 2 和除以 2，仍然是完全的一元函数复合形式，我好了。\n",
    "\n",
    "有了这个理论基础我们就可以编写代码，利用题目提供的 `poc.py`，我们可以编写如下解题脚本："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[35.82831063699986, 80.6361952659885, 88.59694036265915]\n",
      "flag{you_are_good_at_using_calculators_by_amiya}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import requests\n",
    "host = \"http://202.38.93.241:10024\"\n",
    "\n",
    "class Calc:\n",
    "    def __init__(self):\n",
    "        self.ops = []\n",
    "        \n",
    "    def op(self, name, *kws):\n",
    "        if name in dir(self):\n",
    "            getattr(self, name)(*kws)\n",
    "        else:\n",
    "            self.ops.append(name)\n",
    "    \n",
    "    def mul2(self):\n",
    "        # 乘以 2\n",
    "        self.op('exp')\n",
    "        self.op('x^2')\n",
    "        self.op('log')\n",
    "        \n",
    "    def div2(self):\n",
    "        # 除以 2\n",
    "        self.op('exp')\n",
    "        self.op('sqrt')\n",
    "        self.op('log')\n",
    "    \n",
    "    def add1(self):\n",
    "        # 利用双曲函数公式加 1\n",
    "        self.op('div2')\n",
    "        self.op('sqrt')\n",
    "        self.op('asinh')\n",
    "        self.op('mul2')\n",
    "        self.op('cosh')\n",
    "        \n",
    "    def addn(self, n):\n",
    "        # 利用多次加 1 加 n\n",
    "        for _ in range(n):\n",
    "            self.op('add1')\n",
    "\n",
    "def solve(x):\n",
    "    # 二进制小数点后的位数\n",
    "    n = 20\n",
    "    calc = Calc()\n",
    "    \n",
    "    # 利用除以 2 （相当于二进制右移）构造小数部分\n",
    "    for i in range(n):\n",
    "        if int(x * 2 ** (n - i)) % 2:\n",
    "            calc.op('add1')\n",
    "        calc.op('div2')\n",
    "    \n",
    "    calc.op('addn', int(x))\n",
    "    \n",
    "    seq = ','.join(calc.ops)\n",
    "    return seq\n",
    "\n",
    "\n",
    "with requests.session() as sess:\n",
    "    r = sess.get(host + '/challenges')\n",
    "    X = json.loads(r.text)[\"msg\"]\n",
    "    print(X)\n",
    "    data = {\n",
    "        \"a1\": solve(X[0]),\n",
    "        \"a2\": solve(X[1]),\n",
    "        \"a3\": solve(X[2])\n",
    "    }\n",
    "    r = sess.post(host + \"/submit\", data=data)\n",
    "    resp = json.loads(r.text)\n",
    "    print(resp[\"msg\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 解法三：连分数\n",
    "\n",
    "有人可能要问：能不能再给力一点？\n",
    "\n",
    "上面的二进制解法基本可以满足比较高的精度要求，但是有没有一个甚至没有理论误差的方法？\n",
    "\n",
    "答案是显然的：如果我们有一个数的精确连分数表达，那么我们可以用题目限制下的函数构造没有理论误差的计算公式。\n",
    "\n",
    "关于连分数请参看 [维基百科：连分数](https://zh.wikipedia.org/wiki/%E8%BF%9E%E5%88%86%E6%95%B0)，对于有理数，连分数分解算法可以用简单的辗转相除得到，我们这里将目标近似为一个有理数（这里引入了部分误差），然后对其进行连分数分解，最后利用 `addn` 和 `1/x` 完成连分数的计算："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[8.537001664460819, 13.772290777666662, 90.89072584304898]\n",
      "flag{you_are_good_at_using_calculators_by_amiya}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import requests\n",
    "host = \"http://202.38.93.241:10024\"\n",
    "\n",
    "import sympy\n",
    "EPS = 1e-7\n",
    "\n",
    "class Calc:\n",
    "    def __init__(self):\n",
    "        self.ops = []\n",
    "        \n",
    "    def op(self, name, *kws):\n",
    "        if name in dir(self):\n",
    "            getattr(self, name)(*kws)\n",
    "        else:\n",
    "            self.ops.append(name)\n",
    "    \n",
    "    def mul2(self):\n",
    "        # 乘以 2\n",
    "        self.op('exp')\n",
    "        self.op('x^2')\n",
    "        self.op('log')\n",
    "        \n",
    "    def div2(self):\n",
    "        # 除以 2\n",
    "        self.op('exp')\n",
    "        self.op('sqrt')\n",
    "        self.op('log')\n",
    "    \n",
    "    def add1(self):\n",
    "        # 利用双曲函数公式加 1\n",
    "        self.op('div2')\n",
    "        self.op('sqrt')\n",
    "        self.op('asinh')\n",
    "        self.op('mul2')\n",
    "        self.op('cosh')\n",
    "    \n",
    "    def addn(self, n):\n",
    "        # 利用多次加 1 加 n\n",
    "        for _ in range(n):\n",
    "            self.op('add1')\n",
    "\n",
    "def solve(x):\n",
    "    # 将小数化为分数\n",
    "    p = int(x / EPS)\n",
    "    q = int(p / x)\n",
    "    \n",
    "    # 计算连分数数组\n",
    "    cfs = sympy.ntheory.continued_fraction.continued_fraction_periodic(p, q, 0)\n",
    "    \n",
    "    # 利用 addn 和 1/x 计算连分数\n",
    "    calc = Calc()\n",
    "    for k in cfs[::-1]:\n",
    "        calc.op('addn', k)\n",
    "        calc.op('1/x')\n",
    "    calc.op('1/x')\n",
    "    \n",
    "    seq = ','.join(calc.ops)\n",
    "    return seq\n",
    "\n",
    "\n",
    "with requests.session() as sess:\n",
    "    r = sess.get(host + '/challenges')\n",
    "    X = json.loads(r.text)[\"msg\"]\n",
    "    print(X)\n",
    "    data = {\n",
    "        \"a1\": solve(X[0]),\n",
    "        \"a2\": solve(X[1]),\n",
    "        \"a3\": solve(X[2])\n",
    "    }\n",
    "    r = sess.post(host + \"/submit\", data=data)\n",
    "    resp = json.loads(r.text)\n",
    "    print(resp[\"msg\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 附录\n",
    "\n",
    "这道题为了降低难度让很多低精度方法也能通过，所以应该有很多近似方法可以解出本题，如果你有有趣的解法欢迎 PR 投稿。\n",
    "\n",
    "在比赛过程中也有很多人询问为什么会 system error，我想把这个问题连同下面的思考题一起留作课后作业：\n",
    "\n",
    "- 为什么会 system error？\n",
    "\n",
    "- 为什么在构造 `add1` 时，选择了 $[1]\\ \\cosh 2x\\ =[2]\\  \\cosh^2 x + \\sinh^2 x =[3]\\  2\\cosh^2 x - 1 = [4]\\ 2\\sinh^2 x + 1$ 中的 $[1] = [4]$ 而不是 $[1] = [3]$ ？\n",
    "\n",
    "题目代码已经上传到本目录 `src` 下。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [default]",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
